今天我們分享class
是如何生成的,其實關鍵都在type
這個built-in
。
type
type
有兩種常用的使用情況:
type
。class
,是一種動態生成class
的方式。type
的signature
如下:
type(cls_name, cls_bases, cls_dict)
cls_name
為想建立class
的名字。cls_bases
為新建立class
的MRO
上所有class
,為一tuple
。如果給予一個空的tuple
,Python會自動將object
加進去,即(object, )
。cls_dict
含有想建立class
的所有資訊。type
建立class
# 01
class MyClass:
def __init__(self, x):
self.x = x
# 01
是一個基本的class
,我們可以將其拆為幾個部份,分別對應利用type
建立class
的過程:
class
是能夠建立class
這種型態的關鍵字,在這裡我們想用type
取代。cls_name
為'MyClass'
。MyClass
為繼承object
而來,即cls_bases
為(object, )
或以空tuple
代替。MyClass
內含有function
或attribute
的部份為cls_body
。其實是一堆字串,我們需要一個方法來轉化這些字串,將轉換後的狀態記錄於cls_dict
,使其能為type
所用,而內建的exec
正好可以派上用場。# 02
模擬利用type
建立class
。
# 02
cls_dict = {}
cls_body = '''
def __init__(self, x):
self.x = x
'''
exec(cls_body, globals(), cls_dict) # populating cls_body into cls_dict
cls_name = 'MyClass'
cls_bases = ()
MyClass = type(cls_name, cls_bases, cls_dict)
MyType
建立class
既然type
是一種class
,表示我們可以繼承type
建立一個新的MyType
。這麼一來,我們就可以overwrite type.__new__
,在建立新class
的前後動點手腳,最後再用類似前面type
建立class
的語法,使用MyType
來建立class
。
# 03
class MyType(type):
def __new__(mcls, cls_name, _cls_bases, cls_dict):
cls = super().__new__(mcls, cls_name, _cls_bases, cls_dict)
return cls
... # 中間過程如# 02
MyClass = MyType(cls_name, cls_bases, cls_dict)
print(type(MyClass)) # <class '__main__.MyType'>
class關鍵字
搭配MyType
作為metaclass
可以看出使用class
關鍵字來建立class
,比使用MyType
來的簡潔方便。但使用MyType
的好處,是可以在建立class
時動點手腳。那麼有沒有一種方法是既可以使用class 關鍵字
來建立class
,又同時具備我們在MyType
中所做的操作呢?其實有的,而且我們一直隱性在用。
# 04
中的MyClass1
、MyClass2
及MyClass3
是等義的。class
是繼承object
,並由type
所生成。
# 04
class MyClass1:
def __init__(self, x):
self.x = x
class MyClass2(object):
def __init__(self, x):
self.x = x
class MyClass3(object, metaclass=type):
def __init__(self, x):
self.x = x
所以我們只要將metaclass
換為MyType
,就可以繼續使用方便的class 關鍵字
來建立class
並同時具備於MyType
中的操作。
# 04
...
class MyType(type):
def __new__(mcls, cls_name, cls_bases, cls_dict):
cls = super().__new__(mcls, cls_name, cls_bases, cls_dict)
return cls
class MyClass(object, metaclass=MyType):
def __init__(self, x):
self.x = x
__prepare__
本日內容至此,不知道您有沒有疑惑過下面這個問題。
當我們利用MyType
來建立class
,其cls_dict
是我們給予的。但是當利用class關鍵字
搭配MyType
作為metaclass
這種建立class
的方式時,其中的cls_dict
是哪裡來的呢?
其實此cls_dict
會由__prepare__
提供。如果help(type)
,可以看出__prepare__
為class method
。
| Class methods defined here:
|
| __prepare__(...)
| __prepare__() -> dict
| used to create the namespace for the class statement
一般來說,__prepare__
會返回dict
(但事實上可以返回一個mapping
),接著傳遞給__new__
作為其cls_dict
參數(註1
)。然後我們可於__new__
中對cls_dict
進行一些操作。最後當我們使用cls = super().__new__(mcls, cls_name, cls_bases, cls_dict)
時,會將cls_dict
中的東西複製到一個新的dict
中(註2
)。
# 05
class MyType(type):
@classmethod
def __prepare__(cls, cls_name, cls_bases, **kwargs):
print('MyType __prepare__ called')
cls_dict = {}
print(f'{id(cls_dict)=}')
return cls_dict
def __new__(mcls, cls_name, cls_bases, cls_dict):
print('MyType __new__ called')
print(f'{id(cls_dict)=}')
cls = super().__new__(mcls, cls_name, cls_bases, cls_dict)
return cls
class MyClass(metaclass=MyType):
pass
MyType __prepare__ called
id(cls_dict)=2079093125440
MyType __new__ called
id(cls_dict)=2079093125440
由# 05
中我們可以看出,__prepare__
先於__new__
被呼叫,且可以確認__prepare__
中返回的cls_dict
與__new__
中的cls_dict
是同一個obj
。
Python3.7之前,一個有趣的運用是回傳一個collections.OrderedDict
,來保存key-value
的插入順序。但是新版本的dict
已經是有序了,所以除非要維護舊版本的code
,__prepare__
漸漸失去了它的應用價值。有興趣深研的朋友可以參考Python參考文件及PEP 3115。
註1:於__prepare__
傳到__new__
的過程中,Python會幫我們加上一些attribute
,如__qualname__
等在此dict
中。
註2:cls_dict
並非新生成cls
的__dict__
。事實上cls.__dict__
也僅為一mappingproxy
。